﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using Sio.Mdm.Server;
using Sio.Mdm.Client;
using Sio.Mdm.Types;
using Castle.DynamicProxy;
using System.Reflection.Emit;
using System.Collections;
using System.Runtime.Serialization;
using Castle.DynamicProxy.Generators;
//using Castle.DynamicProxy.Builder.CodeGenerators;

namespace Sio.Mdm.Entities
{
    /// <summary>
    /// Implementation of factory for entities.
    /// </summary>
    public static partial class EntityFactory
    {
        #region Construction
        static EntityFactory()
        {
            InitializeFramework();
        } 
        #endregion

        #region Properties

        #region Public
        [NonSerialized]
        private static string _configPath;
        /// <summary>
        /// Get or set path of configuration file. By default 'entities.config' is used
        /// </summary>               
        public static string ConfigPath
        {
            get
            {
                if (String.IsNullOrEmpty(_configPath))
                {
                    _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "entities.config");
                }

                return _configPath;
            }
            set
            {
                _configPath = value;
            }
        } 
        #endregion

        #region Internal
        internal static Dictionary<Type, EntityInfo> _registeredTypes = null;
        internal static Type[] RegisteredTypes
        {
            get
            {
                return EntitiesInfo.Keys.ToArray();
            }            
        }
        internal static Dictionary<Type, EntityInfo> EntitiesInfo
        {
            get
            {
                return _registeredTypes;
            }
        }
        internal static ProxyGenerator _proxy;
        internal static ProxyGenerator Proxy
        {
            get
            {
                if (_proxy == null)
                {
                    _proxy = new ProxyBuilder();
                }

                return _proxy;
            }
            
        }
        internal static EntityInterceptor Intercpetor = new EntityInterceptor();
        #endregion
        
        #endregion

        #region Methods

        #region Public

        #region CreateInstance
        /// <summary>
        /// Create entity instance with optional arguments to be pased to construtor
        /// </summary>
        /// <typeparam name="T">One of <see cref="EntityBase"/> derived class</typeparam>
        /// <param name="arguments">Optional arguments to be passed to constructor</param>
        /// <returns>Instance of required entity.</returns>
        /// <exception cref="Exception">
        /// </exception>
        public static T CreateInstance<T>(params object[] args) where T : class, IEntity
        {
            Type entityType = typeof(T);
            
            if (!EntitiesInfo.ContainsKey(entityType))
            {
                throw new Exception("Type " + entityType.Name + " is not an entity! Did you forget to derive it from 'IEntity'!");
            }
                        
            IEntityInterceptor instance = CreateInterceptorInstance(entityType);
            T proxyInstance = Proxy.CreateInterfaceProxyWithoutTarget(
                entityType,
                new Type[] {entityType, typeof(IEntity), typeof(ISerializable)}, 
                instance
            ) as T;
            
            instance.ProxyInstance = proxyInstance as IEntity;

            // Set values if any
            PropertyInfo[] props = entityType.GetProperties();
            int count = props.Length > args.Length ?
                args.Length : props.Length;

            for (int i = 0; i < count; i++)
            {
                proxyInstance.SetField(props[i].Name, args[i]);
            }

            return proxyInstance;
        }
        /// <summary>
        /// Create entity instance and load it from database from given expression
        /// </summary>
        /// <typeparam name="T">Type of entity to load</typeparam>
        /// <param name="criteria">Criteria to use for loading</param>
        /// <returns>Instance of loaded entity. Make sure you check <see cref="IEntity.Error"/></returns>
        public static T LoadEntity<T>(ISioFilter<T> criteria) where T : class, IEntity
        {
            T entity = CreateInstance<T>();
            entity.Load<T>(criteria);
            return entity;
        }
        /// <summary>
        /// Create instance of <see cref="IEntiti"/> of passed type
        /// </summary>
        /// <param name="entityType">Type of entity to create</param>
        /// <param name="args">
        /// Values to use for initialization in order 
        /// decalred in <see cref="IEntity"/>
        /// </param>
        /// <returns>
        /// Instance of entity
        /// </returns>
        /// <exception cref="ArgumentException">
        /// Type passed does not implement <see cref="IEntity"/> interface
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Passed <paramref name="entityType"/> is null
        /// </exception>
        public static IEntity CreateInstance(Type entityType, params object[] args)
        {
            if (entityType == null)
            {
                throw new ArgumentNullException();
            }

            if (!typeof(IEntity).IsAssignableFrom(entityType))
            {
                throw new ArgumentException("Type " + entityType.Name + " is not an entity! Did you forget to derive it from 'IEntity'!");
            }

            IEntityInterceptor instance = CreateInterceptorInstance(entityType);
            IEntity proxyInstance = Proxy.CreateInterfaceProxyWithoutTarget(
                entityType,
                new Type[] { entityType, typeof(IEntity), typeof(ISerializable) },                
                instance
            ) as IEntity;
            
            instance.ProxyInstance = proxyInstance;

            // Set values if any
            PropertyInfo[] props = entityType.GetProperties();
            int count = props.Length > args.Length ?
                args.Length : props.Length;

            for(int i=0; i < count; i++)
            {
                proxyInstance.SetField(props[i].Name, args[i]);
            }

            return proxyInstance;
        }        
        #endregion        

        #region GetEntityService
        /// <summary>
        /// Get instance of service implementing buisness logic for
        /// <see cref="IEntity"/> declared with <see cref="EntityServiceAttribute"/>
        /// </summary>
        /// <typeparam name="T">One of registered entity types</typeparam>
        /// <returns>Instance of service</returns>
        /// <see cref="EntityServiceAttribute"/>
        public static IService GetEntityService<T>() where T : IEntity
        {
            return GetEntityService(typeof(T));
        }
        public static IService GetEntityService(Type entityType)
        {
            return ServiceFactory.GetService(entityType);
            return EntitiesInfo.ContainsKey(entityType)?
                ServiceFactory.GetService(EntitiesInfo[entityType].ServiceType):
                null;
        }
        public static IService GetEntityService(string name)
        {
            return ServiceFactory.GetService(name);
        }
        public static IService GetEntityService(EntityAttribute attrib)
        {
            if (attrib.InterfaceType == null || attrib.CallByName)
            {
                attrib.InterfaceType = ServiceFactory.GetServiceDefaultInterface(attrib.InterfaceTypeName);
                //attrib.CallByName = true;

                return GetEntityService(attrib.InterfaceTypeName);
            }
            else
            {
                return GetEntityService(attrib.InterfaceType);
            }
        }
        #endregion

        #region GetEntityFields
        /// <summary>
        /// Get all fields for passed entity type
        /// </summary>
        /// <typeparam name="T">Type of entity to retreive fields for</typeparam>
        /// <returns>Collection of fields' names</returns>        
        public static List<string> GetEntityFields<T>() where T : IEntity
        {
            return GetEntityFields(typeof(T));
        }
        /// <summary>
        /// Get all fields for passed entity type
        /// </summary>
        /// <param name="entityType">Type of entity to retreive fields for</param>
        /// <returns>Collection of fields' names</returns>
        /// <exception cref="ArgumentException">
        /// Type is not interface and can not be casted to <see cref="IEntity"/>
        /// </exception>
        public static List<string> GetEntityFields(Type entityType)
        {
            if (!typeof(IEntity).IsAssignableFrom(entityType))
            {
                throw new ArgumentException(String.Format("Type {0} is not entity. Did you forget to add 'IEntity' interface?"));
            }

            List<string> result = new List<string>();

            foreach (KeyValuePair<string, FieldAttribute> pair in EntitiesInfo[entityType].Fields)
            {
                result.Add(pair.Value.Name);
            }

            return result;
        }
        #endregion

        #region GetEntityInfo
        /// <summary>
        /// Get normalized information for passed entity type
        /// </summary>
        /// <typeparam name="T">Implementation of <see cref="IEntity"/> type</typeparam>
        /// <returns></returns>
        public static EntityInfo GetEntityInfo<T>() where T : IEntity
        {
            return GetEntityInfo(typeof(T));
        }
        /// <summary>
        /// Get normalizes information for passed <paramref name="entityType"/>
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static EntityInfo GetEntityInfo(Type entityType)
        {
            return EntitiesInfo[entityType];
        }
        #endregion

        #endregion

        #region Private

        #region InitializeFramework
        private static void InitializeFramework()
        {
            List<Assembly> initAssemblies = new List<Assembly>();
            Version emptyVersion = new Version(0, 0, 0, 0);

            _registeredTypes = new Dictionary<Type, EntityInfo>();

            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                AssemblyName name = asm.GetName();

                // To skip in-memory generated assemblies (ver=0.0.0.0 - some cryptic names and defenetly generated)
                if (name.Version == emptyVersion)
                {
                    continue;
                }

                // To skip base assemblies
                if (name.Name == "Sio.Mdm.Entities" ||
                    name.Name.StartsWith("System") ||
                    name.Name.StartsWith("Microsoft"))
                {
                    continue;
                }

				try
				{
	                foreach (Type type in asm.GetExportedTypes())
	                {
	                    // Never mind types that don't imeplement us
	                    if (!typeof(IEntity).IsAssignableFrom(type))
	                    {
	                        continue;
	                    }
	
	                    _registeredTypes.Add(type, new EntityInfo(type));
	                }
				}
				catch(Exception ex)
				{
					ex.ToString();
				}
            }

            // Because we're creating in-memory (dynmaic) types framework sometimes fails to 
            // find dynamic assemblies created. So let's help it...
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        }
        #endregion

        #region IsGenericCollection
        private static Type IsGenericCollection(Type type)
        {   
            Type[] types = type.GetInterfaces();

            foreach (Type t in types)
            {
                // Check for IEntityCollection and return 
                // entity type from generic types
                if (t.IsGenericType && t.Name.StartsWith("IEntityCollection"))
                {
                    return t.GetGenericArguments()[0];
                }
            }

            return null;
        }
        #endregion

        #region CreateInterceptorInstance
        private static IEntityInterceptor CreateInterceptorInstance(Type type)
        {
            return new EntityInterceptor(type);
            
            //Type entityType = IsGenericCollection(type);

            //if (entityType == null)
            //{
            //    return new EntityInterceptor(type);
            //}

            //Type interceptorType =
            //    typeof(EntityCollectionInterceptor<>).MakeGenericType(entityType);

            //return Activator.CreateInstance(interceptorType, entityType) as IEntityInterceptor;
        }
        #endregion

        #region Initialize
        internal static void Initialize()
        {
        }
        #endregion

        #region CurrentDomain_AssemblyResolve
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly asm in asms)
            {
                if (asm.FullName == args.Name)
                {
                    return asm;
                }
            }
            return null;
        }  
        #endregion

        #endregion 

        #endregion
    }

    #region Customization of proxy generation to add deserilization ctor
    class ProxyBuilder : Castle.DynamicProxy.ProxyGenerator
    {
        public ProxyBuilder()            
        {
        }
    }

    class InterfaceProxyWithoutTargetWithDeserialization : IProxyBuilder
    {
        public InterfaceProxyWithoutTargetWithDeserialization(ModuleScope scope, Type theInterface)
        {
        }

        #region IProxyBuilder Members

        public Type CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Type CreateClassProxy(Type classToProxy, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Type CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, Type targetType, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
        {
            throw new NotImplementedException();
        }

        public Castle.Core.Logging.ILogger Logger
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public ModuleScope ModuleScope
        {
            get { throw new NotImplementedException(); }
        }

        #endregion
    }
    #endregion
}
